import configparser
import difflib
import os
import time
from dataclasses import dataclass, field

import requests
import xlrd
from lxml import etree
from openpyxl import Workbook, load_workbook

from constant import SOFTWARE_WITH_RESPONSIBLE_TEAM

BRANCH_ISSUE = {}


@dataclass
class BaseData:
    """
    基础数据模型,存储版本分支信息,关联issue链接,关联repo源链接以及最终从repo源提出的package信息
    """

    branch: str = ""
    issue: str = ""
    repo_update: str = ""
    repo_epol: str = ""
    packages: list = field(default_factory=list)


@dataclass
class TableRow:
    """
    sheet表中行信息 model
    """

    cve_id: str = ""
    issue_id: str = ""
    repo: str = ""
    status: str = ""
    score: str = ""
    version: str = ""
    abi_changed: str = ""
    owner: str = ""
    src_rpm_name: str = ""


@dataclass
class BugfixRow:
    """
    sheet表中行信息 model
    """

    issue_id: str = ""
    repo: str = ""
    status: str = ""
    owner: str = ""
    src_rpm_name: str = ""


def parse_source_package_name_from_repo_url(basedata: BaseData):
    """
    解析repo源中源码包名信息
    """

    def parse_src_rpm_info(url, src_rpm_list):
        try:
            response = requests.get(f"{url}/source/Packages/")
        except Exception:
            return

        if response.status_code != requests.codes["ok"]:
            return
        # # 存入本地
        # with open("issue.html", mode="w", encoding="utf8") as issue_html_file:
        #     issue_html_file.write(repo_html_code)
        html = etree.HTML(response.text)
        # 提取tbody/tr/td/a标签中的href属性的值
        source_package_name_list = html.xpath("//tbody/tr/td/a[@href]/text()")
        # 存入repo info
        source_package_name: str
        for source_package_name in source_package_name_list:
            if source_package_name.endswith(".src.rpm"):
                src_rpm_list.append(source_package_name)

    parse_src_rpm_info(basedata.repo_update, basedata.packages)
    parse_src_rpm_info(basedata.repo_epol, basedata.packages)
    if basedata.packages:
        print(f"提取{basedata.branch}源码包信息完成")
    else:
        print(f"提取{basedata.branch}源码包信息失败")


def parse_cve_table_from_issue_html(sheet_name, url):
    """
    提取ISSUE链接中表格的信息，返回涵盖表头TableRow对象列表
    """
    response = requests.get(url)
    if response.status_code != requests.codes["ok"]:
        print(f"{sheet_name}issue网页信息获取失败")
        return []
    # 存入本地
    # with open("issue.html", mode="w", encoding="utf8") as issue_html_file:
    #     issue_html_file.write(response.text)
    html = etree.HTML(response.text)

    # 本地读取调试
    # html_code = ""
    # with open("issue.html", mode="r", encoding="utf8") as f:
    #     html_code = ".".join(f.readlines())
    # html = etree.HTML(html_code)
    issue_set = set()  # 建立issue set用于分析,
    table_body_rows = html.xpath("//*[@id='git-issue']/div[3]/div[1]/div[4]/div/div/div[1]/div[1]/table[1]/tbody/tr")
    table_row_list = [TableRow("CVE", "issue", "仓库", "status", "score", "version", "abi是否变化", "责任田", "是否转测软件包")]
    try:
        for row in table_body_rows:
            tmp_table_row = TableRow()

            cve_id_info = row.xpath("td/a/text()")
            # 提取CVE信息失败时,直接跳过
            # 优化: 全局变量统计异常数据点
            if not cve_id_info:
                continue
            tmp_table_row.cve_id = cve_id_info[0].split(":")[1]

            issue_id_info = row.xpath("td/a/@href")
            tmp_table_row.issue_id = str(issue_id_info[0])[-6:]
            issue_set.add(str(issue_id_info[0])[-6:])

            # 提取其余信息,其余信息无特殊标志,因此提取信息数量少于目标信息数量时,直接跳过
            # 优化: 全局变量统计异常数据点
            info = row.xpath("td/text()")
            if len(info) < 4:
                continue
            tmp_table_row.abi_changed = info[-1].strip()
            tmp_table_row.version = info[-2].strip()
            tmp_table_row.score = info[-3].strip()
            tmp_table_row.status = info[-4].strip()
            tmp_table_row.repo = info[-5].strip()
            tmp_table_row.owner = SOFTWARE_WITH_RESPONSIBLE_TEAM.get(tmp_table_row.repo, "")
            table_row_list.append(tmp_table_row)
        print(f"{sheet_name}CVE表格信息提取完成")
    except Exception as error:
        print(error)
        print(f"{sheet_name}CVE表格信息解析失败")

    missing_issue_list = list(set(BRANCH_ISSUE.get(sheet_name, [])) - issue_set)
    if missing_issue_list:
        print(f"版本分支:{sheet_name}, 缺失ISSUE ID: {missing_issue_list}\t/add cve {' '.join(missing_issue_list)}")

    return table_row_list, parse_bugfix(html, sheet_name)


def parse_bugfix(html: etree.HTML, sheet_name):
    table_body_rows = html.xpath("//*[@id='git-issue']/div[3]/div[1]/div[4]/div/div/div[1]/div[1]/table[2]/tbody/tr")
    table_row_list = [BugfixRow("issue", "仓库", "status", "责任田", "是否转测软件包")]
    try:
        for row in table_body_rows:
            tmp_table_row = BugfixRow()

            cve_id_info = row.xpath("td/a/text()")
            # 提取CVE信息失败时,直接跳过
            # 优化: 全局变量统计异常数据点
            if not cve_id_info:
                continue

            # 提取其余信息,其余信息无特殊标志,因此提取信息数量少于目标信息数量时,直接跳过
            # 优化: 全局变量统计异常数据点
            info = row.xpath("td/text()")
            if len(info) < 2:
                continue
            tmp_table_row.status = info[-1].strip()
            tmp_table_row.repo = info[-2].strip()
            tmp_table_row.issue_id = cve_id_info[0]
            tmp_table_row.owner = SOFTWARE_WITH_RESPONSIBLE_TEAM.get(tmp_table_row.repo, "")
            table_row_list.append(tmp_table_row)
        print(f"{sheet_name}-bugfix表格信息提取完成")
    except Exception as error:
        print(error)
        print(f"{sheet_name}-bugfix表格信息解析失败")

    return table_row_list


def cve_form_parse(cve_form_path):
    """
    解析待发布报表中,issue与影响版本分支的关系

    Args:
        cve_form_path 待发布cve报表路径
    """
    branch_set = set()
    branch_issue_dic = {
        # "branch": ["issue id"]
    }

    if not os.path.exists(cve_form_path):
        print("待发布cve报表不存在")
        return branch_issue_dic

    workbook = xlrd.open_workbook(cve_form_path)
    sheet = workbook.sheet_by_index(0)
    # 读取所有issue id
    issues = sheet.col_values(1, 1)
    for index, branch_info in enumerate(
        sheet.col_values(7, 1, sheet.nrows)
    ):  # sheet.col_values(7, 1) 读取所有的受影响分支信息,格式为list
        for branch_name in branch_info.split(","):
            if not branch_name:
                continue
            if branch_name.strip() in branch_set:
                branch_issue_dic[branch_name.strip()].append(issues[index])
            else:
                branch_set.add(branch_name.strip())
                branch_issue_dic[branch_name.strip()] = [issues[index]]
    if branch_issue_dic:
        print(f"{cve_form_path}文件中提取版本分支如下")
        print(list(branch_issue_dic.keys()), "\n")
    else:
        print("版本分支关联issue信息 解析失败\n")
    return branch_issue_dic


def guess_cve_form_name(project_path: str = "./"):
    """
    模糊查询CVE报表名称
    """
    import os

    res = difflib.get_close_matches('CVE报表', os.listdir(project_path), 3, 0.3)
    res.sort(reverse=True)
    if res:
        print(f"猜测需要解析报表名为:{res[0]}")
        return res[0]
    return ""


def load_branch_info_to_basedata(config_path: str):
    config_items = {"issue_url", "repo_update", "repo_epol", "key"}
    branch_need_parse = []
    config = configparser.RawConfigParser()
    try:
        config.read(config_path, encoding="utf8")
    except Exception as error:
        print("配置文件解析失败")
        print(error)
        return branch_need_parse

    for branch_name in config.sections():
        if config_items - set(config[branch_name].keys()):
            print(f"节点{branch_name}缺失目标配置项：{config_items - set(config[branch_name].keys())}")
            continue

        if config[branch_name]["key"] == "false":
            continue
        branch_need_parse.append(
            BaseData(
                *(
                    branch_name,
                    config[branch_name]["issue_url"],
                    config[branch_name]["repo_update"],
                    config[branch_name]["repo_epol"],
                )
            )
        )
    return branch_need_parse


def generate_table(basedata: BaseData):
    # 1. 提取issue html 中CVE表格信息
    table_row_object_list, bugfix_table = parse_cve_table_from_issue_html(basedata.branch, basedata.issue)
    # CVE表格信息提取失败直接跳出
    if not table_row_object_list:
        return
    # 2. 提取源码包名信息
    parse_source_package_name_from_repo_url(basedata)
    # 3. 匹配源码包名
    # 执行n*m次,考虑能否优化
    for rpm in basedata.packages:
        for row in table_row_object_list:
            if row.repo in rpm:
                if row.src_rpm_name:
                    # 如果匹配到多个包名,使用$符号分割,便于筛选
                    row.src_rpm_name += "$"
                row.src_rpm_name += rpm

    # bugfix
    for rpm in basedata.packages:
        for row in bugfix_table:
            if row.repo in rpm:
                if row.src_rpm_name:
                    # 如果匹配到多个包名,使用$符号分割,便于筛选
                    row.src_rpm_name += "$"
                row.src_rpm_name += rpm
    # 4. 写入表格
    # 创建或加载目标excel
    excel_file_name = f"{time.strftime('%Y-%m-%d')}.xlsx"
    if not os.path.exists(f"./{excel_file_name}"):
        wb = Workbook()
        wb.save(excel_file_name)
    else:
        wb = load_workbook(excel_file_name)

    # 新建或读取目标sheet
    sheet_name = basedata.branch + "-cve"
    if sheet_name not in wb.sheetnames:
        wb.create_sheet(sheet_name)
    sheet = wb[sheet_name]  # sheet索引从1,1开始,对应A1单元格

    # 逐行写入sheet
    # 追加写入操作
    # for row in table_row_object_list:
    #     sheet.append(
    #         [row.cve_id, row.issue_id, row.repo, row.status, row.score, row.version, row.abi_changed, row.owner,
    #          row.src_rpm_name])

    # 逐个单元格写入,如有对异常数据的单元格进行特殊绘制的需求,可进行完善,可对已有表格对应单元格进行重写
    table_rows = [
        [
            row.cve_id,
            row.issue_id,
            row.repo,
            row.status,
            row.score,
            row.version,
            row.abi_changed,
            row.owner,
            row.src_rpm_name,
        ]
        for row in table_row_object_list
    ]
    for row_index in range(len(table_rows)):
        for col_index in range(len(table_rows[row_index])):
            sheet.cell(row_index + 1, col_index + 1, table_rows[row_index][col_index])

    if "Sheet" in wb.sheetnames:
        wb.remove(wb["Sheet"])

    # 保存bugfix
    sheet_name = basedata.branch + "-bugfix"
    if sheet_name not in wb.sheetnames:
        wb.create_sheet(sheet_name)
    sheet = wb[sheet_name]  # sheet索引从1,1开始,对应A1单元格

    # 逐行写入sheet
    # 追加写入操作
    # for row in table_row_object_list:
    #     sheet.append(
    #         [row.cve_id, row.issue_id, row.repo, row.status, row.score, row.version, row.abi_changed, row.owner,
    #          row.src_rpm_name])

    # 逐个单元格写入,如有对异常数据的单元格进行特殊绘制的需求,可进行完善,可对已有表格对应单元格进行重写
    table_rows = [
        [
            row.issue_id,
            row.repo,
            row.status,
            row.owner,
            row.src_rpm_name,
        ]
        for row in bugfix_table
    ]
    for row_index in range(len(table_rows)):
        for col_index in range(len(table_rows[row_index])):
            sheet.cell(row_index + 1, col_index + 1, table_rows[row_index][col_index])

    wb.save(excel_file_name)


if __name__ == '__main__':
    # 可手动传入待发布CVE报表文件路径,替换guess掉函数即可, guess函数利用 difflib 模糊匹配
    BRANCH_ISSUE = cve_form_parse(guess_cve_form_name())
    for branch in load_branch_info_to_basedata("config.ini"):
        generate_table(branch)
        print()
        time.sleep(2)
